home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue45 / Alfresco / AALZSWin.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  2000-11-02  |  12.5 KB  |  377 lines

  1. {*********************************************************}
  2. {* AALZSWin                                              *}
  3. {* Copyright (c) Julian M Bucknall 1998-1999             *}
  4. {* All rights reserved.                                  *}
  5. {*********************************************************}
  6. {* Algorithms Alfresco LZ77 unit - sliding window        *}
  7. {*********************************************************}
  8.  
  9. {Note: this unit is released as freeware. In other words, you are free
  10.        to use this unit in your own applications, however I retain all
  11.        copyright to the code. JMB}
  12.  
  13. unit AALZSWin;
  14.  
  15. interface
  16.  
  17. uses
  18.   SysUtils,
  19.   Classes,
  20.   AALZBase;
  21.  
  22. {Notes: TaaLZSlidingWindow implements a sliding window on data for
  23.         the Algorithms Alfresco LZ77 implementation. The sliding
  24.         window class can be used in two ways: for compression and for
  25.         decompression.
  26.  
  27.         For compression, the stream passed to the class is
  28.         automatically read when required to keep the internal buffer
  29.         fully loaded. The property GetNextKey returns the 3 character
  30.         string at the current position ready for the hashing routine.
  31.         (Note that right at the end of the input stream a two or less
  32.         character string will be returned - this is a signal to end
  33.         the compression phase.) Advance will move the sliding window
  34.         on by one character (or as many that are specified - depends
  35.         on the match). Compare will compare the string at the current
  36.         position with that at the given offset (note that this is an
  37.         offset into the input stream and not the position in the
  38.         sliding window) and will return the number of matched
  39.         characters.
  40.  
  41.         For decompression, the stream passed to the class is the
  42.         output stream (the decompressed stream). The decompressor will
  43.         either call AddChar to add a literal character at the current
  44.         position and advance by one, or will call AddCode to decode
  45.         a distance/length pair and advance the stream on by as many
  46.         characters as is decoded. The class will periodically update
  47.         the stream from the internal buffer.
  48.         }
  49.  
  50.  
  51. {This compiler define adds some extra checking at the start of each
  52.  interfaced method to ensure that the sliding window is used in the
  53.  correct mode.}
  54. {$IFOPT D+}
  55.   {$DEFINE InDebugMode}
  56. {$ENDIF}
  57.  
  58. type
  59.   TaaLZSlidingWindow = class
  60.     private
  61.       swCompressing : boolean;
  62.       swStart       : PChar;
  63.       swCurrent     : PChar;
  64.       swLookAheadEnd: PChar;
  65.       swMidPoint    : PChar;
  66.       swBuffer      : PChar;
  67.       swBufferEnd   : PChar;
  68.       swStream      : TStream;
  69.       swStartOffset : longint;
  70.     protected
  71.       procedure swAdvanceAfterAdd(aCount : integer);
  72.       procedure swReadFromStream;
  73.       procedure swSetCapacity(aValue : longint);
  74.       procedure swWriteToStream(aFinalBlock : boolean);
  75.     public
  76.       constructor Create(aStream      : TStream;
  77.                          aCompressing : boolean);
  78.       destructor Destroy; override;
  79.  
  80.       {methods used for both compression and decompression}
  81.       procedure Clear;
  82.  
  83.       {methods used during compression}
  84.       procedure Advance(aCount : integer);
  85.       function Compare(aOffset   : longint;
  86.                    var aDistance : integer) : integer;
  87.       procedure GetNextKey(var aMS     : TaaLZKey;
  88.                            var aOffset : longint);
  89.  
  90.       {methods used during decompression}
  91.       procedure AddChar(aCh : char);
  92.       procedure AddCode(aDistance : integer; aLength : integer);
  93.   end;
  94.  
  95. implementation
  96.  
  97. {Notes:
  98.         Meaning of the internal pointers:
  99.  
  100.         |----------+===================+==+--------------------------|
  101.         |          |                   |  |                          |
  102.         swBuffer   swStart     swCurrent  swLookAheadEnd   swBufferEnd
  103.  
  104.         The valid data is between swStart and swLookAheadEnd when
  105.         compressing, and between swStart and swCurrent when
  106.         decompressing (swLookAheadEnd not being used in this latter
  107.         case). Usually the difference between swStart and swCurrent is
  108.         4096, the size of the sliding window.
  109.         }
  110.  
  111. {===Helper routines==================================================}
  112. procedure NotEnoughDataError(aAvail, aReq : integer);
  113. begin
  114.   raise Exception.Create(
  115.           Format('Not enough data in queue (%s bytes) to satisfy read request (%s bytes)',
  116.                  [aAvail, aReq]));
  117. end;
  118. {--------}
  119. procedure CannotWriteError(aToWrite, aWritten : integer);
  120. begin
  121.   raise Exception.Create(
  122.           Format('Attempted to write %d bytes to stream, but only %d bytes got written',
  123.                  [aToWrite, aWritten]));
  124. end;
  125. {--------}
  126. procedure InvalidUseError(aCompressing : boolean;
  127.                     const aMethodName  : string);
  128. const
  129.   CompressStr : array [boolean] of string[13] =
  130.                 ('decompression', 'compression');
  131. begin
  132.   raise Exception.Create(
  133.            Format('%s method cannot be used during %s',
  134.                   [aMethodName, CompressStr[aCompressing]]));
  135. end;
  136. {====================================================================}
  137.  
  138.  
  139. {===TaaLZSlidingWindow=============================================}
  140. constructor TaaLZSlidingWindow.Create(aStream      : TStream;
  141.                                       aCompressing : boolean);
  142. begin
  143.   inherited Create;
  144.   {save parameters}
  145.   swCompressing := aCompressing;
  146.   swStream := aStream;
  147.   {set capacity of sliding window: by definition this is 4096 bytes of
  148.    sliding window and 18 bytes of lookahead}
  149.   swSetCapacity(aalzSlidingWindowSize + aalzLookAheadSize);
  150.   {reset the buffer and, if we're compressing, read some data from the
  151.    stream to be compressed}
  152.   Clear;
  153.   if aCompressing then
  154.     swReadFromStream;
  155. end;
  156. {--------}
  157. destructor TaaLZSlidingWindow.Destroy;
  158. begin
  159.   if Assigned(swBuffer) then begin
  160.     {finish writing to the output stream if we're decompressing}
  161.     if not swCompressing then
  162.       swWriteToStream(true);
  163.     {free the buffer}
  164.     FreeMem(swBuffer, swBufferEnd - swBuffer);
  165.   end;
  166.   inherited Destroy;
  167. end;
  168. {--------}
  169. procedure TaaLZSlidingWindow.AddChar(aCh : char);
  170. begin
  171.   {$IFDEF InDebugMode}
  172.   {this is a decompression routine only}
  173.   if swCompressing then
  174.     InvalidUseError(swCompressing, 'AddChar');
  175.   {$ENDIF}
  176.   {add the character to the buffer}
  177.   swCurrent^ := aCh;
  178.   {advance the start of the sliding window}
  179.   swAdvanceAfterAdd(1);
  180. end;
  181. {--------}
  182. procedure TaaLZSlidingWindow.AddCode(aDistance : integer; aLength : integer);
  183. var
  184.   FromChar : PChar;
  185.   ToChar   : PChar;
  186.   i        : integer;
  187. begin
  188.   {$IFDEF InDebugMode}
  189.   {this is a decompression routine only}
  190.   if swCompressing then
  191.     InvalidUseError(swCompressing, 'AddCode');
  192.   {$ENDIF}
  193.   {set up the pointers to do the data copy; note we cannot use Move
  194.    since part of the data we are copying may be set up by the actual
  195.    copying of the data}
  196.   FromChar := swCurrent - aDistance;
  197.   ToChar := swCurrent;
  198.   for i := 1 to aLength do begin
  199.     ToChar^ := FromChar^;
  200.     inc(FromChar);
  201.     inc(ToChar);
  202.   end;
  203.   {advance the start of the sliding window}
  204.   swAdvanceAfterAdd(aLength);
  205. end;
  206. {--------}
  207. procedure TaaLZSlidingWindow.Advance(aCount : integer);
  208. var
  209.   ByteCount   : integer;
  210. begin
  211.   {$IFDEF InDebugMode}
  212.   {this is a compression routine only}
  213.   if not swCompressing then
  214.     InvalidUseError(swCompressing, 'Advance');
  215.   {$ENDIF}
  216.   {advance the start of the sliding window, if required}
  217.   if ((swCurrent - swStart) >= aalzSlidingWindowSize) then begin
  218.     inc(swStart, aCount);
  219.     inc(swStartOffset, aCount);
  220.   end;
  221.   {advance the current pointer}
  222.   inc(swCurrent, aCount);
  223.   {check to see if we have advanced into the overflow zone}
  224.   if (swStart >= swMidPoint) then begin
  225.     {move current data back to the start of the buffer}
  226.     ByteCount := swLookAheadEnd - swStart;
  227.     Move(swStart^, swBuffer^, ByteCount);
  228.     {reset the various pointers}
  229.     ByteCount := swStart - swBuffer;
  230.     swStart := swBuffer;
  231.     dec(swCurrent, ByteCount);
  232.     dec(swLookAheadEnd, ByteCount);
  233.     {read some more data from the stream}
  234.     swReadFromStream;
  235.   end;
  236. end;
  237. {--------}
  238. procedure TaaLZSlidingWindow.Clear;
  239. begin
  240.   swStart := swBuffer;
  241.   swCurrent := swBuffer;
  242.   swLookAheadEnd := swBuffer;
  243.   swStartOffset := 0;
  244. end;
  245. {--------}
  246. function TaaLZSlidingWindow.Compare(aOffset   : longint;
  247.                                 var aDistance : integer) : integer;
  248. var
  249.   MatchStr  : PChar;
  250.   CurrentCh : PChar;
  251. begin
  252.   {Note: when this routine is called it is assumed that at least three
  253.          characters will match between the passed position and the
  254.          current position}
  255.   {$IFDEF InDebugMode}
  256.   {this is a compression routine only}
  257.   if not swCompressing then
  258.     InvalidUseError(swCompressing, 'Compare');
  259.   {$ENDIF}
  260.   {calculate the position in the sliding window for the passed offset
  261.    and its distance from the current position}
  262.   MatchStr := swStart + (aOffset - swStartOffset);
  263.   aDistance := swCurrent - MatchStr;
  264.   inc(MatchStr, 3);
  265.   {calculate the length of the matching characters between this and
  266.    the current position. Don't go above the maximum length. Have a
  267.    special case for the end of the input stream}
  268.   Result := 3;
  269.   CurrentCh := swCurrent + 3;
  270.   if (CurrentCh <> swLookAheadEnd) then begin
  271.     while (Result < aalzMaxMatchLength) and
  272.           (MatchStr^ = CurrentCh^) do begin
  273.       inc(Result);
  274.       inc(MatchStr);
  275.       inc(CurrentCh);
  276.       if (CurrentCh = swLookAheadEnd) then
  277.         Break;
  278.     end;
  279.   end;
  280. end;
  281. {--------}
  282. procedure TaaLZSlidingWindow.GetNextKey(var aMS     : TaaLZKey;
  283.                                         var aOffset : longint);
  284. var
  285.   P : PChar;
  286.   i : integer;
  287. begin
  288.   {$IFDEF InDebugMode}
  289.   {this is a compression routine only}
  290.   if not swCompressing then
  291.     InvalidUseError(swCompressing, 'GetNextKey');
  292.   {$ENDIF}
  293.   {calculate the length of the match string; usually it's 3, but at
  294.    the end of the input stream it could be 2 or less.}
  295.   if ((swLookAheadEnd - swCurrent) < 3) then
  296.     aMS.AsString[0] := char(swLookAheadEnd - swCurrent)
  297.   else
  298.     aMS.AsString[0] := #3;
  299.   P := swCurrent;
  300.   for i := 1 to length(aMS.AsString) do begin
  301.     aMS.AsString[i] := P^;
  302.     inc(P);
  303.   end;
  304.   aOffset := swStartOffset + (swCurrent - swStart);
  305. end;
  306. {--------}
  307. procedure TaaLZSlidingWindow.swAdvanceAfterAdd(aCount : integer);
  308. begin
  309.   {advance the start of the sliding window, if required}
  310.   if ((swCurrent - swStart) >= aalzSlidingWindowSize) then begin
  311.     inc(swStart, aCount);
  312.     inc(swStartOffset, aCount);
  313.   end;
  314.   {advance the current pointer}
  315.   inc(swCurrent, aCount);
  316.   {check to see if we have advanced into the overflow zone}
  317.   if (swStart >= swMidPoint) then begin
  318.     {write some more data to the stream (from swBuffer to swStart)}
  319.     swWriteToStream(false);
  320.     {move current data back to the start of the buffer}
  321.     Move(swStart^, swBuffer^, swCurrent - swStart);
  322.     {reset the various pointers}
  323.     dec(swCurrent, swStart - swBuffer);
  324.     swStart := swBuffer;
  325.   end;
  326. end;
  327. {--------}
  328. procedure TaaLZSlidingWindow.swReadFromStream;
  329. var
  330.   BytesRead   : longint;
  331.   BytesToRead : longint;
  332. begin
  333.   {read some more data into the look ahead zone}
  334.   BytesToRead := swBufferEnd - swLookAheadEnd;
  335.   BytesRead := swStream.Read(swLookAheadEnd^, BytesToRead);
  336.   inc(swLookAheadEnd, BytesRead);
  337. end;
  338. {--------}
  339. procedure TaaLZSlidingWindow.swSetCapacity(aValue : longint);
  340. var
  341.   NewQueue  : PChar;
  342. begin
  343.   {round the requested capacity to nearest 64 bytes}
  344.   aValue := (aValue + 63) and $7FFFFFC0;
  345.   {get a new buffer}
  346.   GetMem(NewQueue, aValue * 2);
  347.   {destroy the old buffer}
  348.   if (swBuffer <> nil) then
  349.     FreeMem(swBuffer, swBufferEnd - swBuffer);
  350.   {set the head/tail and other pointers}
  351.   swBuffer := NewQueue;
  352.   swStart := NewQueue;
  353.   swCurrent := NewQueue;
  354.   swLookAheadEnd := NewQueue;
  355.   swBufferEnd := NewQueue + (aValue * 2);
  356.   swMidPoint := NewQueue + aValue;
  357. end;
  358. {--------}
  359. procedure TaaLZSlidingWindow.swWriteToStream(aFinalBlock : boolean);
  360. var
  361.   BytesWritten : longint;
  362.   BytesToWrite : longint;
  363. begin
  364.   {write the data before the current sliding window}
  365.   if aFinalBlock then
  366.     BytesToWrite := swCurrent - swBuffer
  367.   else
  368.     BytesToWrite := swStart - swBuffer;
  369.   BytesWritten := swStream.Write(swBuffer^, BytesToWrite);
  370.   if (BytesWritten <> BytesToWrite) then
  371.     CannotWriteError(BytesToWrite, BytesWritten);
  372. end;
  373. {====================================================================}
  374.  
  375. end.
  376.  
  377.